package com.suray.wcs.service.util;

import com.suray.basic.wcs.path.core.Node;
import com.suray.basic.wcs.path.core.Route;
import com.suray.basic.wcs.path.core.api.impl.DifFloorRouteFactoryImp;
import com.suray.basic.wcs.path.core.api.impl.RouteFactoryImp;
import com.suray.basic.wcs.path.enums.Direction;
import com.suray.basic.wcs.path.enums.NodeStatus;
import com.suray.basic.wcs.path.enums.NodeType;
import com.suray.basic.wcs.path.exception.CreatePathFailedException;
import com.suray.basic.wcs.plc.pojo.oper.lifter.LifterOper;
import com.suray.basic.wcs.time.bo.WCSTimeModuleBO;
import com.suray.basic.wcs.utils.Coord;
import com.suray.basic.wcs.utils.LoggerUtil;
import com.suray.commin.core.utils.SpringContextHolder;
import com.suray.system.run.ding.DingRobot;
import com.suray.wcs.res.enums.RgvStatus;
import com.suray.wcs.service.core.util.LockNodeManager;
import com.suray.wcs.service.enums.*;
import com.suray.wcs.service.pojo.Rgv;
import com.suray.wcs.service.system.bo.ConnectNodeBO;
import com.suray.wcs.service.system.bo.NodeAndPlanBO;
import com.suray.wcs.service.system.bo.NotificationBO;
import com.suray.wcs.service.system.entity.ConnectConfDB;
import com.suray.wcs.service.system.entity.NodeDB;
import com.suray.wcs.service.system.entity.PortDB;
import com.suray.wcs.service.system.entity.TaskDB;
import com.suray.wcs.service.system.service.impl.ConnectConfServiceImpl;
import com.suray.wcs.service.system.service.impl.NodeDBServiceImpl;
import com.suray.wcs.service.system.service.impl.NotificationServiceImpl;
import com.suray.wcs.service.system.service.impl.TaskDBServiceImpl;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

public class WcsUtil {

    public static final String COLON_CONNECTOR = ":";

    public static final String HTTP_CONNECTOR = "http://";


    /**
     * 小铃铛显示信息
     * @param message
     */
    public static void notification(String message) {
        DingRobot.pauseMessage(message);
        NotificationServiceImpl notificationService = SpringContextHolder.getBean(NotificationServiceImpl.class);
        NotificationBO notificationBO = new NotificationBO();
        notificationBO.setContent(message);
        notificationBO.setNotificationType(NotificationTypeEnum.WARN.getType());
        notificationBO.setIsRead(false);
        notificationBO.setSeverityLevel(1);
        notificationService.insertNotification(notificationBO);
    }

    /**
     * 小铃铛显示信息
     * @param message
     * @param notifiType
     */
    public static void notification(String message, NotificationTypeEnum notifiType) {
        NotificationServiceImpl notificationService = SpringContextHolder.getBean(NotificationServiceImpl.class);
        NotificationBO notificationBO = new NotificationBO();
        notificationBO.setContent(message);
        notificationBO.setNotificationType(notifiType.getType());
        notificationBO.setIsRead(false);
        notificationBO.setSeverityLevel(1);
        notificationService.insertNotification(notificationBO);
    }

    /**
     * 生成任务
     * @param startNode
     * @param endNode
     * @param taskType
     * @param taskService
     * @param wmsTaskId
     * @param clazz
     * @return
     */
    public static boolean generalTask(Node startNode, Node endNode, Integer rgvNo, String district, TaskType taskType
            , TaskDBServiceImpl taskService, String wmsTaskId, Class<?> clazz) {
        TaskDB taskdb = new TaskDB();

        if (startNode != null) {
            taskdb.setStartNode(startNode);
        }
        taskdb.setEndNode(endNode);
        taskdb.setTaskType(taskType);
        taskdb.setProcessingRgvNo(rgvNo);
        taskdb.setWmsTaskId(wmsTaskId);

        taskService.insertTask(Collections.singletonList(taskdb));
        if (rgvNo == null) {
            LoggerUtil.info(taskType.getDescription() + "任务生成成功", clazz);
        } else {
            LoggerUtil.info(rgvNo + "号小车, " + taskType.getDescription() + "任务生成成功", clazz);
        }
        return true;
    }

    /**
     * 生成任务
     * @param startNode
     * @param endNode
     * @param rgv
     * @param taskType
     * @param taskService
     * @param clazz
     * @return
     */
    public static TaskDB generalTask(Node startNode, Node endNode, Rgv rgv, TaskType taskType,
                                     TaskDBServiceImpl taskService, Integer priority, Class<?> clazz) {
        if (rgv == null) {
            LoggerUtil.info("无小车，" + taskType.getDescription() + "任务生成失败", clazz);
            return null;
        }

        TaskDB taskdb = new TaskDB();

        if (startNode != null) {
            taskdb.setStartNode(startNode);
        }
        taskdb.setEndNode(endNode);
        taskdb.setTaskType(taskType);
        taskdb.setProcessingRgvNo(rgv.getRgvDB().getRgvNo());

        if (priority != null) {
            taskdb.setPriorityCode(priority);
        }

        taskService.insertTask(Collections.singletonList(taskdb));
        LoggerUtil.info(rgv.getRgvName() + taskType.getDescription() + "任务生成成功", clazz);
        return taskdb;
    }



    /**
     * 生成任务
     * @param startNode
     * @param endNode
     * @param rgv
     * @param taskType
     * @param taskService
     * @param clazz
     * @return
     */
    public static TaskDB generalTask(Node startNode, Node endNode, Rgv rgv, TaskType taskType,
                                     TaskRgvProgress rgvProgress, TaskDBServiceImpl taskService, Integer priority,
                                     Class<?> clazz) {
        if (rgv == null) {
            LoggerUtil.info("无小车，" + taskType.getDescription() + "任务生成失败", clazz);
            return null;
        }

        TaskDB taskdb = new TaskDB();

        if (startNode != null) {
            taskdb.setStartNode(startNode);
        }
        taskdb.setEndNode(endNode);
        taskdb.setTaskType(taskType);
        taskdb.setProcessingRgvNo(rgv.getRgvDB().getRgvNo());

        if (priority != null) {
            taskdb.setPriorityCode(priority);
        }

        if (rgvProgress != null) {
            taskdb.setRgvProgress(rgvProgress);
        }

        taskService.insertTask(Collections.singletonList(taskdb));
        LoggerUtil.info(rgv.getRgvName() + taskType.getDescription() + "任务生成成功", clazz);
        return taskdb;
    }

    /**
     * 生成任务
     * @param startNode
     * @param endNode
     * @param rgv
     * @param taskType
     * @param taskService
     * @param clazz
     * @return
     */
    public static TaskDB generalTask(Node startNode, Node endNode, Rgv rgv, TaskType taskType, TaskDBServiceImpl taskService, Class<?> clazz) {
        return generalTask(startNode, endNode, rgv, taskType, taskService, null, clazz);
    }

    /**
     * 判断节点是否有货，有货返回true
     * @param coord
     * @param nodeService
     * @return
     */
    public static boolean isNodeHasCargo(Coord coord, NodeDBServiceImpl nodeService) {

        NodeDB nodedb = nodeService.getNodeDB(coord);

        if (nodedb.getGridStatus().equals(NodeStatus.Y.toString())) {
            return true;
        }

        return false;
    }

    /**
     * 判断节点是否有货，有货返回true
     * @param coord
     * @param nodeService
     * @return
     */
    public static boolean changeNodeCargoStatus(Coord coord, NodeDBServiceImpl nodeService, boolean cargoState) {
        if (cargoState) {
            return nodeService.updateNodeDBStatus(coord, NodeStatus.Y.toString());
        } else {
            return nodeService.updateNodeDBStatus(coord, NodeStatus.N.toString());
        }
    }

    /**
     * 判断节点是否有货，有货返回true
     *
     *
     * @param lifterOper
     * @param node
     * @param nodeService
     * @return
     */
    public static boolean changeNodeCargoStatus(LifterOper lifterOper, Node node, NodeDBServiceImpl nodeService, boolean cargoState) {
        Integer realNodeZ = lifterOper.getGridzRelative() + node.getZ() - 1;
        Node realNode = new Node(node.getX(), node.getY(), realNodeZ);
        if (cargoState) {
            LoggerUtil.info(realNode + "修改为有货", WcsUtil.class);
            return nodeService.updateNodeDBStatus(realNode, NodeStatus.Y.toString());
        } else {
            LoggerUtil.info(realNode + "修改为无货", WcsUtil.class);
            return nodeService.updateNodeDBStatus(realNode, NodeStatus.N.toString());
        }
    }

    /**
     * 生成移库任务，如果目标位置有货则返回false，否则生成移库任务并返回true
     * @param srcNode
     * @param destNode
     * @param rgv
     * @param taskService
     * @param clazz
     * @return
     */
    public static boolean generalTransTask(Node srcNode, Node destNode, Rgv rgv, NodeDBServiceImpl nodeService, TaskDBServiceImpl taskService, Class<?> clazz) {
        if (isNodeHasCargo(destNode, nodeService)) {
            LoggerUtil.info(destNode.toString() + "位置有货，等待货物移走", clazz);
            return false;
        }

        //生成移库任务
        LoggerUtil.info(rgv.getRgvName() + "-----生成移库任务，起点" + srcNode.toString() + ", 终点" + destNode.toString(), clazz);
        generalTask(srcNode, destNode, rgv, TaskType.TRANSFER, taskService, clazz);

        return true;
    }

    public static Node getLifterCarNode(int location, int floor, boolean isNormalRgv) {
        if (floor > 10) {
            return new Node(location, 0, floor);
        }

        int offsetFloor;

        if (isNormalRgv) {
            offsetFloor = 2;
        } else {
            offsetFloor = 1;
        }

        return new Node(location, 0, floor * 10 + offsetFloor);
    }

    /**
     * 睡眠
     * @param time:时间（ms）
     */
    public static void delay(long time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * list打印
     * @param objs
     * @return
     */
    public static String listToString(List<?> objs) {
        StringBuffer sb = new StringBuffer();

        objs.forEach(obj -> {
            sb.append(obj.toString());
            sb.append(",");
        });

        return sb.toString();
    }

    /**
     * map打印
     * @param objs
     * @return
     */
    public static String mapToString(Map<?, ?> objs) {
        StringBuffer sb = new StringBuffer();

        Iterator itKey = objs.keySet().iterator();
        Iterator itValues = objs.values().iterator();

        while (itKey.hasNext()) {
            Object objKey = itKey.next();
            Object objValue = itValues.next();
            sb.append(objKey.toString());
            sb.append(",");
            sb.append(objValue.toString());
            sb.append(",");
        }

        return sb.toString();
    }

    public static boolean isLifterCarPro() {
        return true;
    }

    public static void checkException(Rgv rgv, String msg) throws RuntimeException {
        if (rgv.getRgvStatus() == RgvStatus.OFF_LINE || rgv.getRgvStatus() == RgvStatus.ERROR) {
            throw new RuntimeException(msg);
        }
    }

    public static void exceptionLog(Throwable e, Class<?> clazz) {
        LoggerUtil.error("{}", e.getMessage(), e, clazz);
    }

    public static NodeType getNodeType(Node node, NodeDBServiceImpl nodedbService) {
        NodeDB nodeDB = nodedbService.getNodeDB(node);
        return nodeDB.getGridType();
    }

    /**
     * Rgv上方扫码结果是否有货
     * @param barcode：0xffff1f 无货，其他有货
     * @return
     */
    public static boolean isBarcodeHasCargo(int barcode) {
        if (barcode == 0xffff1f) {
            return false;
        }

        return true;
    }

    /**
     * 给出起点和终点给出路径规划
     * @param startNode
     * @param endNode
     * @param district
     * @param isPalletUp
     * @return
     */
    public static Route findRout(Node startNode, Node endNode, String district, boolean isPalletUp, WCSTimeModuleBO wcsTimeModuleBO) {
        NodeDBServiceImpl nodedbService = SpringContextHolder.getBean(NodeDBServiceImpl.class);

        RouteFactoryImp routeFactory = new RouteFactoryImp(nodedbService.getNodeList(),
                LockNodeManager.getLockedNodes(), wcsTimeModuleBO, 0);

        return routeFactory.createRoute(startNode, endNode, isPalletUp);
    }

    public static String changeToHexString(byte[] data) {
        StringBuffer sb = new StringBuffer();

        for (byte b : data) {
//            sb.append("0x");
            sb.append(byteToHexString(b).toUpperCase());
            sb.append(" ");
//            sb.append(", ");
        }

        return sb.toString();
    }

    private static String byteToHexString(byte b) {
        String str = Integer.toHexString(b);

        if (str.length() > 2) {
            return str.substring(str.length() - 2);
        } else if (str.length() == 1) {
            return "0" + str;
        } else if (str.length() == 0) {
            return "00";
        }

        return str;
    }

    /**
     * 创建文件夹
     *
     * @param path
     * @throws IOException
     */
    public static void createPaper(String path) throws IOException {
        File file = new File(path);
        File fileParent = file.getParentFile();
        if (!fileParent.exists()) {
            fileParent.mkdirs();
        }
        file.createNewFile();
    }

    public static void copy(Object father, Object child) throws Exception {
        fatherToChild(father, child);
    }

    /*
     * 将父类所有的属性COPY到子类中。
     * 类定义中child一定要extends father；
     * 而且child和father一定为严格javabean写法，属性为deleteDate，方法为getDeleteDate
     */
    private static void fatherToChild (Object father,Object child) throws Exception{
        if(!(child.getClass().getSuperclass()==father.getClass())){
            throw new Exception("child不是father的子类");
        }
        Class fatherClass= father.getClass();
        Field ff[]= fatherClass.getDeclaredFields();
        for(int i=0;i<ff.length;i++){
            Field f=ff[i];//取出每一个属性，如deleteDate
            Class type=f.getType();
            Method m=fatherClass.getMethod("get"+upperHeadChar(f.getName()));//方法getDeleteDate
            Object obj=m.invoke(father);//取出属性值
            f.set(child,obj);
        }
    }
    /**
     * 首字母大写，in:deleteDate，out:DeleteDate
     */
    private static String upperHeadChar(String in){
        String head=in.substring(0,1);
        String out=head.toUpperCase()+in.substring(1,in.length());
        return out;
    }

    /**
     * 获取http url
     * @param ip
     * @param port
     * @param path
     * @return
     */
    public static String getHttpUrl(String ip, String port, String path) {
        return new StringBuilder().append(HTTP_CONNECTOR).append(ip).append(COLON_CONNECTOR).append(port).append(path).toString();
    }

    /**
     * 检测任务路径是否可行
     * @param taskDB
     * @param rgv
     * @return
     */
    public static List<Route> calcRoad(TaskDB taskDB, Rgv rgv, NodeDBServiceImpl nodeDBService) {
        List<Integer> layers = new ArrayList<>();
        layers.add(taskDB.selectStartNode().getZ());
        layers.add(taskDB.selectEndNode().getZ());
        DifFloorRouteFactoryImp routeFactory =
                new DifFloorRouteFactoryImp(nodeDBService.getNodeByLayer(layers),
                        LockNodeManager.getLockedNodesExceptSelf(rgv), rgv.generateWCSTimeModuleBO(),
                        rgv.getRgvDB().getRgvType());
        try {
            return routeFactory.createRoute(taskDB.selectStartNode(), taskDB.selectEndNode(),
                    taskDB.getTaskType() == TaskType.MOVE_UNLOADED
                            || taskDB.getTaskType() == TaskType.CHARGE ? false : true);
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (CreatePathFailedException e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 根据WMS发送的任务获取小车的终点
     */
    public static NodeDB getRgvEndNode(Node startNode, PortDB portDB, TaskType taskType,
                                       NodeDBServiceImpl nodeDBService) {
        NodeAndPlanBO nodeAndPlanBO = null;
        if(!ConnectModel.NEED_CONNECT.getValue().equals(portDB.getConnectMode())){
            nodeAndPlanBO = nodeDBService.getMostAccessibleNodeAndPlan(startNode.toString(), portDB.getPortCode(),
                    taskType);
            if(nodeAndPlanBO == null){
                throw new RuntimeException("无法根据库口找到合适的起/终点");
            }
        }

        return nodeAndPlanBO != null ? nodeAndPlanBO.getNodedb() : null;
    }

    /**
     * 入库和出库根据WMS发送的任务获取最优的接驳配置
     */
    public static ConnectNodeBO getConnectNodeInOrOut(String startStr, String endStr, NodeDB start, NodeDB end,
                                                Integer taskType, NodeDBServiceImpl nodeDBService,
                                                ConnectConfServiceImpl connectConfService) {
        // 接驳点方案集合
        List<ConnectConfDB> connectDBList;
        ConnectNodeBO connectNodeBO = null;

        if (taskType.equals(TaskType.IN.getTypeNum())) {
            Node node = new Node(end.getGridx(), end.getGridy(), end.getGridz());
            connectDBList = connectConfService.findByType(PortModel.IN_PORT.getValue());
            // 最适合的入库接驳配置方案
            connectNodeBO = nodeDBService.getMostConnectNodeAndPlan(startStr, node.toString(),
                    TaskType.parseTaskType(taskType), connectDBList);
        } else if (taskType.equals(TaskType.OUT.getTypeNum())) {
            Node node = new Node(start.getGridx(), start.getGridy(), start.getGridz());
            connectDBList = connectConfService.findByType(PortModel.OUT_PORT.getValue());
            // 最适合的出库接驳配置方案
            connectNodeBO = nodeDBService.getMostConnectNodeAndPlan(node.toString(), endStr,
                    TaskType.parseTaskType(taskType), connectDBList);
        }
        return connectNodeBO;
    }

    /**
     * 解析行驶方向
     * @param preNode
     * @param aftNode
     * @return
     */
    public static Direction analyzeDir(Node preNode, Node aftNode) {
        if (preNode.getZ() != aftNode.getZ()) {
            throw new RuntimeException("两节点不在同一层，不能解析行驶方向:" + preNode + aftNode);
        } else if (preNode.equals(aftNode)) {
            throw new RuntimeException("两节点相同，不能解析行驶方向:" + preNode + aftNode);
        }
        if (preNode.getX() == aftNode.getX()) {
            if (preNode.getY() > aftNode.getY()) {
                return Direction.LEFT;
            } else {
                return Direction.RIGHT;
            }
        } else {
            if (preNode.getX() > aftNode.getX()) {
                return Direction.UP;
            } else {
                return Direction.DOWN;
            }
        }
    }

}
